import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  BeforeInsert,
  Index,
  ManyToMany,
  JoinTable,
  OneToMany,
} from 'typeorm';
import {
  IsOptional,
  IsString,
  MaxLength,
  IsNotEmpty,
  IsEmail,
  IsMobilePhone,
  IsEnum,
  IsDateString,
  Length,
  IsISO8601,
} from 'class-validator';
import { CrudValidationGroups } from '@nestjsx/crud';
import { Exclude, Type } from 'class-transformer';
import * as bcrypt from 'bcrypt';
import { UserRole, Gender, UserStatus } from '@/enum';
import { Artist } from '@/modules/resources/entities/artist.entity';
import { Song } from '@/modules/resources/entities/song.entity';
import { Album } from '@/modules/resources/entities/album.entity';
import { ArtistEdition } from '@/modules/resources/entities/artist-edition.entity';
import { AlbumEdition } from '@/modules/resources/entities/album-edition.entity';
import { PlayList } from '@/modules/resources/entities/play-list.entity';
import { Score } from '@/modules/resources/entities/score.entity';
import { Post } from '@/modules/resources/entities/post.entity';
import { Track } from '@/modules/resources/entities/track.entity';

const { CREATE, UPDATE } = CrudValidationGroups;
@Entity({ name: 'users' })
export class User {
  @IsOptional({ always: true })
  @PrimaryGeneratedColumn()
  id: number;

  @IsOptional({ groups: [UPDATE] })
  @IsNotEmpty({ groups: [CREATE] })
  @IsString({ always: true })
  @Length(2, 30)
  @Index({ unique: true })
  @Column({ type: 'character varying', length: 30, unique: true })
  username: string;

  @Exclude()
  @IsOptional({ groups: [UPDATE] })
  @IsNotEmpty({ groups: [CREATE] })
  @IsString()
  @Column({ type: 'character', length: 60 })
  password?: string;

  @IsOptional({ always: true })
  @IsEmail({}, { always: true })
  @MaxLength(60, { always: true })
  @Index({ unique: true })
  @Column({
    type: 'character varying',
    length: 60,
    unique: true,
    nullable: true,
  })
  email: string;

  @IsOptional({ always: true })
  @IsDateString({ always: true })
  @Column({ type: 'timestamp without time zone', nullable: true })
  emailValidatedAt: Date;

  @IsOptional({ always: true })
  @IsMobilePhone('zh-CN', { always: true })
  @Index({ unique: true })
  @Column({
    type: 'character varying',
    length: 15,
    unique: true,
    nullable: true,
  })
  phone: string;

  @IsOptional({ always: true })
  @IsDateString({ always: true })
  @Column({ type: 'timestamp without time zone', nullable: true })
  phoneValidatedAt: Date;

  @IsOptional({ always: true })
  @IsString({ always: true })
  @Column({ type: 'character varying', length: 100, nullable: true })
  avatar: string;

  @IsOptional({ always: true })
  @IsEnum(Gender, { always: true })
  @Column({ type: 'enum', enum: Gender, nullable: true })
  gender: Gender;

  @IsOptional({ always: true })
  @IsISO8601({}, { always: true })
  @Column({ type: 'date', nullable: true })
  birthday: Date;

  @IsOptional({ always: true })
  @IsString({ always: true })
  @Column({ type: 'character varying', nullable: true })
  introduction: string;

  @IsOptional({ always: true })
  @IsEnum(UserRole, { each: true, always: true })
  @Column({
    type: 'enum',
    enum: UserRole,
    array: true,
    default: [UserRole.USER],
  })
  roles: UserRole[];

  @IsOptional({ always: true })
  @Column({ type: 'enum', enum: UserStatus, default: UserStatus.ACTIVE })
  status: UserStatus;

  @Type(_type => Artist)
  @ManyToMany(
    _type => Artist,
    artist => artist.followers,
  )
  @JoinTable()
  followedArtists: Artist[];

  @Type(_type => Song)
  @ManyToMany(
    _type => Song,
    song => song.favByUsers,
  )
  @JoinTable()
  favSongs: Song[];

  @Type(_type => Album)
  @ManyToMany(
    _type => Album,
    album => album.favByUsers,
  )
  @JoinTable()
  favAlbums: Album[];

  @Type(_type => PlayList)
  @OneToMany(
    _type => PlayList,
    playlist => playlist.belongsToUser,
  )
  playLists: PlayList[];

  @Type(_type => ArtistEdition)
  @OneToMany(
    _type => ArtistEdition,
    edition => edition.editor,
  )
  editedArtistEditions: ArtistEdition[];

  @Type(_type => AlbumEdition)
  @OneToMany(
    _type => AlbumEdition,
    edition => edition.editor,
  )
  editedAlbumEditions: AlbumEdition[];

  @Type(_type => Score)
  @OneToMany(
    _type => Score,
    score => score.uploadedByUser,
  )
  uploadedScores: Score[];

  @Type(_type => Track)
  @OneToMany(
    _type => Track,
    track => track.uploadedByUser,
  )
  uploadedTracks: Track[];

  @Type(_type => Post)
  @OneToMany(
    _type => Post,
    post => post.poster,
  )
  posts: Post[];

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @BeforeInsert()
  async b4insert?() {
    this.password = await bcrypt.hash(this.password, 10);
  }
}
